package de.homer.server.util;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Repository;

import de.homer.adapter.domain.PluginMetaInformation;
import de.homer.adapter.interfaces.HomeRPlugin;

/**
 * @author André Stannek
 * 
 */
@Repository
public class PluginClassLoader {

	/**
	 * 
	 */
	private final Map<String, Class<? extends HomeRPlugin>> loadedClasses;

	/**
	 * 
	 */
	private final Map<String, String> jarFileIndex;

	/**
	 * 
	 */
	public PluginClassLoader() {
		loadedClasses = new HashMap<>();
		jarFileIndex = new HashMap<>();
	}

	/**
	 * Tries to load the {@link Class} for a {@link HomeRPlugin} from the
	 * classpath.
	 * 
	 * @param name
	 *            the name of the {@link Class} to load
	 * @return the {@link Class} for the given classname
	 * @throws PluginClassLoadingException
	 *             if the {@link Class} couldn't be loaded
	 */
	public final Class<? extends HomeRPlugin> loadPluginClass(final String name)
			throws PluginClassLoadingException {
		String jarFile = jarFileIndex.get(name);
		if (jarFile == null) {
			throw new PluginClassLoadingException("Jar file for class unknown");
		}
		return loadPluginClass(name, new File(jarFile));
	}

	/**
	 * Tries to load the {@link Class} for a {@link HomeRPlugin} from a given
	 * jar file.
	 * 
	 * @param name
	 *            the name of the {@link Class} to load
	 * @param jarFile
	 *            the path to the jar file that contains the plugin class
	 * @return the {@link Class} from the given jar file for the given classname
	 * @throws PluginClassLoadingException
	 *             if the {@link Class} couldn't be loaded
	 */
	public final Class<? extends HomeRPlugin> loadPluginClass(
			final String name, final String jarFile)
			throws PluginClassLoadingException {
		return loadPluginClass(name, new File(jarFile));
	}

	/**
	 * Tries to load the {@link Class} for a {@link HomeRPlugin} from given
	 * {@link PluginMetaInformation}.
	 * 
	 * @param desriptor
	 *            the descriptor of the plugin to load
	 * @return the {@link Class} for the given {@link PluginMetaInformation}
	 * @throws PluginClassLoadingException
	 *             if the {@link Class} couldn't be loaded
	 */
	public final Class<? extends HomeRPlugin> loadPluginClass(
			final PluginMetaInformation desriptor)
			throws PluginClassLoadingException {
		return loadPluginClass(desriptor.getPluginClassName(),
				desriptor.getJarPath());
	}

	/**
	 * Tries to load the {@link Class} for a {@link HomeRPlugin} from a given
	 * jar file.
	 * 
	 * @param name
	 *            the name of the {@link Class} to load
	 * @param jarFile
	 *            the {@link File} that points to the jar file
	 * @return the {@link Class} from the given jar file for the given classname
	 * @throws PluginClassLoadingException
	 *             if the {@link Class} couldn't be loaded
	 */
	@SuppressWarnings("unchecked")
	public final Class<? extends HomeRPlugin> loadPluginClass(
			final String name, final File jarFile)
			throws PluginClassLoadingException {

		Class<?> loadedClass = loadedClasses.get(name);
		Class<? extends HomeRPlugin> result = null;
		if (loadedClass == null) {
			try (URLClassLoader urlLoader = new URLClassLoader(new URL[] { jarFile
					.toURI().toURL() }, getClass().getClassLoader())){
				loadedClass = urlLoader.loadClass(name);
				if (HomeRPlugin.class.isAssignableFrom(loadedClass)
						&& !loadedClass.isInterface()
						&& !Modifier.isAbstract(loadedClass.getModifiers())) {
					jarFileIndex.put(name, jarFile.getAbsolutePath());
					result = (Class<? extends HomeRPlugin>) loadedClass;
					loadedClasses.put(name, result);
				}
			} catch (ClassNotFoundException | IOException e) {
				throw new PluginClassLoadingException(
						"Plugin Class could not be loaded from jar", e);
			}
		}
		return result;
	}
}
